﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pifagor.IG;
using System.IO;
using Pifagor.Exceptions;

namespace Pifagor
{
	public static class PifagorHelper
	{
		#region Public members
		public static IGFile LoadIG(string fileName)
		{
			var file = new IGFile();

			using (var sr = new StreamReader(fileName))
			using (var br = new BinaryReader(sr.BaseStream))
			{
				file.Functions = readEntityArray<IGFunction>(br, readFunction);
				file.ExternalTypes = readEntityArray<IGExternalType>(br, readExternalType);
				file.RootNodes = readEntityArray<IGRootNode>(br, readRootNode);
				file.DelayedLists = readEntityArray<IGDelayedList>(br, readDelayedList);
				file.Nodes = readEntityArray<IGNode>(br, readNode);
			}

			return file;
		}
		#endregion

		#region Private read entities functions
		static IGFunction readFunction(BinaryReader br)
		{
			return new IGFunction
			{
				Name = readString(br)
			};
		}

		static IGExternalType readExternalType(BinaryReader br)
		{
			return new IGExternalType { Name = readString(br) };
		}

		static IGRootNode readRootNode(BinaryReader br)
		{
			return new IGRootNode { Id = br.ReadInt32() };
		}

		static IGDelayedList readDelayedList(BinaryReader br)
		{
			return new IGDelayedList { Id = br.ReadInt32() };
		}

		static IGNode readNode(BinaryReader br)
		{
			var res = new IGNode();
			res.Id = br.ReadInt32();
			res.Type = readEnumValue<IGNodeType>(br, "Node type");
			res.DelayedListId = br.ReadInt32();
			res.Links = readEntityArray<IGNodeLink>(br, readNodeLink);
			if (res.Type == IGNodeType.Constant)
				res.ConstantValue = readConstant(br);

			return res;
		}

		static IGConstant readConstant(BinaryReader br)
		{
			var res = new IGConstant();
			res.Type = readEnumValue<IGConstantType>(br, "Constant type");
			switch (res.Type)
			{
				case IGConstantType.FunctionArgument:
					res.IsUndefined = true;
					break;
				case IGConstantType.Integer:
					res.IntegerValue = br.ReadInt32();
					break;
				case IGConstantType.InternalFunction:
					res.InternalFunctionType = readEnumValue<IGInternalFunctionType>(br, "Internal function type");
					break;
				case IGConstantType.ExternalFunction:
					res.ExternalFunctionId = br.ReadInt32();
					break;
				default:
					throw new FormatReadException("Unrecognized constant type");
			}
			return res;
		}

		static IGNodeLink readNodeLink(BinaryReader br)
		{
			return new IGNodeLink { Id = br.ReadInt32() };
		}
		#endregion

		#region Private static helper methods
		static T[] readEntityArray<T>(BinaryReader br, Func<BinaryReader, T> readFunc) where T : class
		{
			var count = br.ReadInt32();
			if (count < 0)
				throw new FormatReadException();

			T[] res = new T[count];
			for (int i = 0; i < count; i++)
				res[i] = readFunc(br);
			return res;
		}

		static string readString(BinaryReader br)
		{
			int nameLen = br.ReadInt32();
			var nameDataWCharT = br.ReadBytes(nameLen * 2);
			var nameChars = Encoding.Unicode.GetChars(nameDataWCharT);

			return new string(nameChars);
		}

		static TEnum readEnumValue<TEnum>(BinaryReader br, string enumDescription) where TEnum : struct, IConvertible
		{
			var type = typeof(TEnum);
			if (!type.IsEnum)
				throw new ArgumentException("Enum type must be passed");

			var enumValue = br.ReadInt32();
			if (!Enum.IsDefined(type, enumValue))
				throw new FormatReadException(string.Format("{0} {1} not recognized", enumDescription, enumValue));

			return (TEnum)Enum.ToObject(type, enumValue);
		}
		#endregion
	}
}
